
package stratego.java.ssm.piece;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.HashMap;

/**
 * <code>public final class PieceUtilities</code>
 * <p>
 * Contains a few variables and methods for constructing Pieces.
 * </p>
 * 
 * @author Mark Aligbe
 * 
 */
public final class PieceUtilities
{
	// Shape variables that control the look and feel of chips
	private static double	                                    innerPercent	= 0.80;
	private static double	                                    iconPercent	  = 0.20;
	private static double	                                    outerPercent	= 0.87;
	private static double	                                    shadowPercent	= 0.92;
	private static int	                                        thickness	  = 2;
	private static int	                                        arcSize	      = 125;
	/** A Color[3] containing a blueGradient. Usually given to player 2 */
	public static final Color[]	                                blueGradient	= {
	        new Color(0, 67, 192, 255), new Color(0, 126, 224, 255), new Color(0, 30, 99, 255)};
	/** A Color[3] containing a redGradient. Usually given to player 1 */
	public static final Color[]	                                redGradient	  = {
	        new Color(192, 57, 0, 255), new Color(244, 73, 0, 255), new Color(55, 15, 0, 255)};
	/**
	 * A HashMap&ltString, Class&lt? extends Piece&gt&gt that maps the name of the Piece to the
	 * Class object of the Piece.
	 */
	public static final HashMap<String, Class<? extends Piece>>	PIECES	      =
	                                                                                  new HashMap<String, Class<? extends Piece>>();
	// Populating the pieces mapping.
	static
	{
		PIECES.put("Marshal", MarshalPiece.class);
		PIECES.put("General", GeneralPiece.class);
		PIECES.put("Colonel", ColonelPiece.class);
		PIECES.put("Major", MajorPiece.class);
		PIECES.put("Captain", CaptainPiece.class);
		PIECES.put("Lieutenant", LieutenantPiece.class);
		PIECES.put("Sergeant", SergeantPiece.class);
		PIECES.put("Miner", MinerPiece.class);
		PIECES.put("Scout", ScoutPiece.class);
		PIECES.put("Spy", SpyPiece.class);
		PIECES.put("Bomb", BombPiece.class);
		PIECES.put("Flag", FlagPiece.class);
		PIECES.put("Block", BlockPiece.class);
	}
	
	// A Utility class
	private PieceUtilities() throws AssertionError
	{
		throw new AssertionError();
	}
	
	/**
	 * <code>public static Image drawChipAndIcon(Image img, Image icon)</code>
	 * <p>
	 * Draws the given icon on the given Image. Uses a preconfigured background generator to set the
	 * image against.
	 * </p>
	 * 
	 * @param img
	 *            - The Image that will contain the icon and background.
	 * @param icon
	 *            - The icon to place on the chip.
	 * @return a reference to img is returned.
	 */
	public static Image drawChipAndIcon(BufferedImage img, Image icon, Color g1, Color g2,
	        Color border)
	{
		// It's not actually that many lines of code, just a lot of wrap-around methods, empty
		// spaces, and copious comments
		
		// Retrieve the graphics object for drawing
		Graphics2D g = (Graphics2D) img.getGraphics();
		// Set the rendering hints for better graphics
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
		        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Set this image to Color(0,0,0,0)
		g.setComposite(AlphaComposite.Clear);
		g.fillRect(0, 0, img.getWidth(null), img.getHeight(null));
		
		// Change the AlphaComposite mode to emulate layers
		g.setComposite(AlphaComposite.SrcOver);
		
		// Draw the Chip
		
		// Draw the drop shadow
		// Set the max iterations
		int width = img.getWidth(null);
		int maxSteps = (int) Math.round(width * shadowPercent - width * outerPercent);
		// Initialize the shadow opacity endpoints
		// Almost transparent
		Color c2 = new Color(0, 0, 0, 10);
		// Half opaque
		Color c1 = new Color(0, 0, 0, 128);
		for (int i = 1; i <= maxSteps; i++)
		{
			g.setColor(new Color(0, 0, 0, c1.getAlpha()
			        - (int) Math.round((c1.getAlpha() - c2.getAlpha()) / (double) maxSteps
			                * (i - 1))));
			g.drawRoundRect(
			        (int) Math.round(img.getWidth(null) * (1.0 - outerPercent) / 2.0 - i / 2.0),
			        (int) Math.round(img.getHeight(null) * (1.0 - outerPercent) / 2.0 - i / 2.0),
			        (int) Math.round(img.getWidth(null) * outerPercent) + i,
			        (int) Math.round(img.getHeight(null) * outerPercent) + i,
			        (int) Math.round(arcSize * shadowPercent),
			        (int) Math.round(arcSize * shadowPercent));
		}
		
		// Draw the black rim border
		g.setColor(border);
		g.fillRoundRect((int) Math.round(img.getWidth(null) * (1.0 - outerPercent) / 2.0),
		        (int) Math.round(img.getHeight(null) * (1.0 - outerPercent) / 2.0),
		        (int) Math.round(img.getWidth(null) * outerPercent) + 1,
		        (int) Math.round(img.getHeight(null) * outerPercent) + 1,
		        (int) Math.round(arcSize * outerPercent), (int) Math.round(arcSize * outerPercent));
		
		// Draw the side gradient
		
		// Initialize the gradient endpoints
		// Set the max iterations
		maxSteps = (int) Math.round(width * outerPercent - width * innerPercent);
		for (int i = thickness; i <= maxSteps; i++)
		{
			g.setColor(new Color(g1.getRed()
			        + (int) Math.round((g2.getRed() - g1.getRed())
			                / (double) (maxSteps - thickness) * (i - thickness)), g1.getGreen()
			        + (int) Math.round((g2.getGreen() - g1.getGreen())
			                / (double) (maxSteps - thickness) * (i - thickness)), g1.getBlue()
			        + (int) Math.round((g2.getBlue() - g1.getBlue())
			                / (double) (maxSteps - thickness) * (i - thickness)), 255));
			// That error of one...
			g.fillRoundRect((int) Math.round(width * (1.0 - outerPercent) / 2.0 + i / 2.0)
			        + thickness, (int) Math.round(width * (1.0 - outerPercent) / 2.0 + i / 2.0)
			        + thickness, (int) Math.round(width * outerPercent) - i - thickness * 2 + 1,
			        (int) Math.round(width * outerPercent) - i - thickness * 2 + 1,
			        (int) Math.round(arcSize * outerPercent),
			        (int) Math.round(arcSize * outerPercent));
		}
		
		// Draw the inner black rim
		g.setColor(border);
		int blackShift = 6;
		g.fillRoundRect((int) Math.round(img.getWidth(null) * (1.0 - innerPercent) / 2.0)
		        + blackShift, (int) Math.round(img.getHeight(null) * (1.0 - innerPercent) / 2.0)
		        + blackShift, (int) Math.round(img.getWidth(null) * innerPercent) - blackShift * 2,
		        (int) Math.round(img.getHeight(null) * innerPercent) - blackShift * 2,
		        (int) Math.round(arcSize * innerPercent), (int) Math.round(arcSize * innerPercent));
		
		// Finally, fill in the middle rounded square
		g.setColor(new Color(248, 248, 248, 255));
		// That error of one...
		g.fillRoundRect(
		        (int) Math.round(img.getWidth(null) * (1.0 - innerPercent) / 2.0) + blackShift * 2,
		        (int) Math.round(img.getHeight(null) * (1.0 - innerPercent) / 2.0) + blackShift * 2,
		        (int) Math.round(img.getWidth(null) * innerPercent) - blackShift * 4 - 1,
		        (int) Math.round(img.getHeight(null) * innerPercent) - blackShift * 4 - 1,
		        (int) Math.round(arcSize * innerPercent), (int) Math.round(arcSize * innerPercent));
		
		// Then, draw the icon on the chip
		g.drawImage(icon,
		        (int) Math.round(img.getWidth(null) * (1.0 - innerPercent + iconPercent) / 2.0),
		        (int) Math.round(img.getHeight(null) * (1.0 - innerPercent + iconPercent) / 2.0),
		        (int) Math.round(img.getWidth(null) * (innerPercent - iconPercent)),
		        (int) Math.round(img.getHeight(null) * (innerPercent - iconPercent)), null);
		
		return img;
	}
	
	/**
	 * <code>public static Image drawChipBack(BufferedImage img, Color g1, Color g2, Color border)</code>
	 * <p>
	 * Draws 3 vertical white, rounded rectangles, outlined in black, with a gradient specified by
	 * g1 and g2, and bordered with the specified color. Also has a drop shadow.
	 * </p>
	 * 
	 * @param img
	 *            - The BufferedImage to draw on, it is also the return.
	 * @param g1
	 *            - The darker Color of the gradient.
	 * @param g2
	 *            - The brighter Color of the gradient.
	 * @param border
	 *            - The Color of the border.
	 * @return The same reference as img.
	 */
	public static Image drawChipBack(BufferedImage img, Color g1, Color g2, Color border)
	{
		// A lot of the code is reused from drawChipAndIcon, because it is pretty much the same. In
		// particular, the drop shadow generation was taken, as well as the rim, but most of the
		// size and position determining code was re-thought to verify initial correctness
		
		double backPercent = 0.65;
		
		// Retrieve the graphics object for drawing
		Graphics2D g = (Graphics2D) img.getGraphics();
		// Set the rendering hints for better graphics
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
		        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		
		// Set this image to Color(0,0,0,0)
		g.setComposite(AlphaComposite.Clear);
		g.fillRect(0, 0, img.getWidth(null), img.getHeight(null));
		
		// Change the AlphaComposite mode to emulate layers
		g.setComposite(AlphaComposite.SrcOver);
		
		// Draw the drop shadow
		// Set the max iterations
		int width = img.getWidth(null);
		int maxSteps = (int) Math.round(width * shadowPercent - width * outerPercent);
		// Initialize the shadow opacity endpoints
		// Almost transparent
		Color c2 = new Color(0, 0, 0, 10);
		// Half opaque
		Color c1 = new Color(0, 0, 0, 128);
		for (int i = 1; i <= maxSteps; i++)
		{
			g.setColor(new Color(0, 0, 0, c1.getAlpha()
			        - (int) Math.round((c1.getAlpha() - c2.getAlpha()) / (double) maxSteps
			                * (i - 1))));
			g.drawRoundRect(
			        (int) Math.round(img.getWidth(null) * (1.0 - outerPercent) / 2.0 - i / 2.0),
			        (int) Math.round(img.getHeight(null) * (1.0 - outerPercent) / 2.0 - i / 2.0),
			        (int) Math.round(img.getWidth(null) * outerPercent) + i,
			        (int) Math.round(img.getHeight(null) * outerPercent) + i,
			        (int) Math.round(arcSize * shadowPercent),
			        (int) Math.round(arcSize * shadowPercent));
		}
		
		// Draw the black rim border (error of 1 again...)
		g.setColor(border);
		g.fillRoundRect((int) Math.round(img.getWidth(null) * (1.0 - outerPercent) / 2.0),
		        (int) Math.round(img.getHeight(null) * (1.0 - outerPercent) / 2.0),
		        (int) Math.round(img.getWidth(null) * outerPercent) + 1,
		        (int) Math.round(img.getHeight(null) * outerPercent) + 1,
		        (int) Math.round(arcSize * outerPercent), (int) Math.round(arcSize * outerPercent));
		
		// Draw the side gradient
		
		// Initialize the gradient endpoints
		// Set the max iterations
		maxSteps = (int) Math.round(width * outerPercent - width * backPercent);
		for (int i = thickness; i <= maxSteps; i++)
		{
			g.setColor(new Color(g1.getRed()
			        + (int) Math.round((g2.getRed() - g1.getRed())
			                / (double) (maxSteps - thickness) * (i - thickness)), g1.getGreen()
			        + (int) Math.round((g2.getGreen() - g1.getGreen())
			                / (double) (maxSteps - thickness) * (i - thickness)), g1.getBlue()
			        + (int) Math.round((g2.getBlue() - g1.getBlue())
			                / (double) (maxSteps - thickness) * (i - thickness)), 255));
			// That error of one...
			g.fillRoundRect((int) Math.round(width * (1.0 - outerPercent) / 2.0 + i / 2.0)
			        + thickness, (int) Math.round(width * (1.0 - outerPercent) / 2.0 + i / 2.0)
			        + thickness, (int) Math.round(width * outerPercent) - i - thickness * 2 + 1,
			        (int) Math.round(width * outerPercent) - i - thickness * 2 + 1,
			        (int) Math.round(arcSize * outerPercent),
			        (int) Math.round(arcSize * outerPercent));
		}
		
		// Draw the vertical stripes (with that off white)
		// Some control variables for the look and feel of the line
		int lineThickness = 8;
		int lineHeight = (int) Math.round(img.getHeight() * innerPercent * 0.80);
		int numLines = 3;
		int offset = 3;
		// Should not be changed
		int displacement = (int) Math.round(width / (double) (numLines + 1));
		for (int i = 1; i <= numLines; i++)
		{
			g.setColor(border);
			g.fillRoundRect(displacement * i - (int) Math.round(lineThickness / 2.0) - offset,
			        (int) Math.round((img.getHeight(null) - lineHeight) / 2.0) - offset,
			        lineThickness + offset * 2, lineHeight + offset * 2,
			        (lineThickness + offset) * 2, (lineThickness + offset) * 2);
			g.setColor(new Color(248, 248, 248, 255));
			g.fillRoundRect(displacement * i - (int) Math.round(lineThickness / 2.0),
			        (int) Math.round((img.getHeight(null) - lineHeight) / 2.0), lineThickness,
			        lineHeight, lineThickness * 2, lineThickness * 2);
		}
		return img;
	}
}
